package com.ctg.itrdc.sysmgr.permission.core.sso;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListener;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.XmlUtils;

/**
 * Performs CAS single sign-out operations in an API-agnostic fashion.
 *
 */
public final class SingleSignOutHandler implements SessionListener {

    /** Logger instance */
    private final Log log = LogFactory.getLog(getClass());

    /** The name of the artifact parameter.  This is used to capture the session identifier. */
    private String artifactParameterName = "ticket";

    /** Parameter name that stores logout request */
    private String logoutParameterName = "logoutRequest";

    private HashMapBackedMappingStorage storage = new HashMapBackedMappingStorage();

    protected SingleSignOutHandler(){
        init();
    }
    /**
     * @param name Name of the authentication token parameter.
     */
    public void setArtifactParameterName(final String name) {
        this.artifactParameterName = name;
    }

    /**
     * @param name Name of parameter containing CAS logout request message.
     */
    public void setLogoutParameterName(final String name) {
        this.logoutParameterName = name;
    }
    protected String getLogoutParameterName() {
        return this.logoutParameterName;
    }

    /**
     * Initializes the component for use.
     */
    public void init() {
        CommonUtils.assertNotNull(this.artifactParameterName, "artifactParameterName cannot be null.");
        CommonUtils.assertNotNull(this.logoutParameterName, "logoutParameterName cannot be null.");
    }

    /**
     * Determines whether the given request contains an authentication token.
     *
     * @param request HTTP reqest.
     *
     * @return True if request contains authentication token, false otherwise.
     */
    public boolean isTokenRequest(final HttpServletRequest request) {
        return CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.artifactParameterName));
    }

    /**
     * Determines whether the given request is a CAS logout request.
     *
     * @param request HTTP request.
     *
     * @return True if request is logout request, false otherwise.
     */
    public boolean isLogoutRequest(final HttpServletRequest request) {
        return "POST".equals(request.getMethod()) && !isMultipartRequest(request) &&
            CommonUtils.isNotBlank(CommonUtils.safeGetParameter(request, this.logoutParameterName));
    }

    /**
     * 记录token与subject的映射
     * @param request HTTP request containing an authentication token.
     */
    public void recordSubject(final HttpServletRequest request) {
        Subject subject = SecurityUtils.getSubject();

        final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName);
        if (log.isDebugEnabled()) {
            log.debug("Recording session for token " + token);
        }

        storage.addSubject(token, subject);
    }

    /**
     * @param request HTTP request containing a CAS logout message.
     */
    public void invalidateSession(final HttpServletRequest request, final SessionManager sessionManager) {
        final String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName);
        if (log.isTraceEnabled()) {
            log.trace ("Logout request:\n" + logoutMessage);
        }

        final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
        if (CommonUtils.isNotBlank(token)) {
            Subject subject = storage.getSubjectByMappingId(token);
            if (subject != null) {
            	Session session = subject.getSession(false);
            	if(session != null) {
            		try {
            			session.setAttribute(logoutParameterName, true);
            			log.trace("corresponding session id:" + subject.getSession(false).getId());
                		subject.logout();
                	} catch (UnknownSessionException unknownSessionException) {
                	// 一次http请求会有多个subject响应，而多个subject可能对应上同一个session
                	// 因此多个logout会操作到同一个session
                		log.trace(unknownSessionException);
                	} catch (NullPointerException nullPointerException) {
                		log.trace(nullPointerException);
                	}
            	}
            }
            storage.removeSubjectByMappingId(token);
        }
    }

    private boolean isMultipartRequest(final HttpServletRequest request) {
        return request.getContentType() != null && request.getContentType().toLowerCase().startsWith("multipart");
    }
    
	@Override
	public void onStart(Session session) {
		log.trace("start session: " + session.getId());
	}
	
	@Override
	public void onStop(Session session) {
		storage.removeSubjectBySessionId(session.getId().toString());
		log.trace("session: " + session.getId() + " has been stopped");
	}
	
	@Override
	public void onExpiration(Session session) {
		storage.removeSubjectBySessionId(session.getId().toString());
		log.trace("session:" + session.getId() + " has expired");
	}
}